package com.java.simple.smallspring.beans.factory.xml;

import cn.hutool.core.util.StrUtil;
import com.java.simple.smallspring.beans.BeansException;
import com.java.simple.smallspring.beans.PropertyValue;
import com.java.simple.smallspring.beans.factory.config.BeanDefinition;
import com.java.simple.smallspring.beans.factory.config.BeanReference;
import com.java.simple.smallspring.beans.factory.support.AbstractBeanDefinitionReader;
import com.java.simple.smallspring.beans.factory.support.BeanDefinitionRegistry;
import com.java.simple.smallspring.context.annotation.ClassPathBeanDefinitionScanner;
import com.java.simple.smallspring.core.io.Resource;
import com.java.simple.smallspring.core.io.ResourceLoader;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * @author zhoujunlin
 * @date 2022年08月20日 20:05
 * @desc
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        super(registry, resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(Resource resource) throws BeansException {
        try {
            try (InputStream inputStream = resource.getInputStream()) {
                doLoadBeanDefinition(inputStream);
            }
        } catch (IOException | ClassNotFoundException | DocumentException e) {
            throw new BeansException("Load BeanDefinition error from resource", e);
        }
    }

    @Override
    public void loadBeanDefinitions(Resource... resources) throws BeansException {
        for (Resource resource : resources) {
            loadBeanDefinitions(resource);
        }
    }

    @Override
    public void loadBeanDefinitions(String location) throws BeansException {
        ResourceLoader resourceLoader = getResourceLoader();
        Resource resource = resourceLoader.getResource(location);
        loadBeanDefinitions(resource);
    }

    @Override
    public void loadBeanDefinitions(String... locations) throws BeansException {
        for (String location : locations) {
            loadBeanDefinitions(location);
        }
    }

    /**
     * 加载BeanDefinition
     * 扫描配置的包以及配置的bean
     *
     * @param inputStream
     * @throws ClassNotFoundException
     * @throws DocumentException
     */
    protected void doLoadBeanDefinition(InputStream inputStream) throws ClassNotFoundException, DocumentException {
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        Element root = document.getRootElement();
        // 解析context:component-scan标签 扫描包中的类并提取相关信息 用于组装BeanDefinition
        Element componentScan = root.element("component-scan");
        if (Objects.nonNull(componentScan)) {
            String scanPath = componentScan.attributeValue("base-package");
            if (StrUtil.isBlank(scanPath)) {
                throw new BeansException("The value of base-package attribute can not be empty or null");
            }
            scanPackage(scanPath);
        }
        List<Element> beanList = root.elements("bean");
        for (Element bean : beanList) {
            String id = bean.attributeValue("id");
            String name = bean.attributeValue("name");
            String className = bean.attributeValue("class");
            // 获取 init-method及destroy-method配置的读取
            String initMethod = bean.attributeValue("init-method");
            String destroyMethod = bean.attributeValue("destroy-method");
            String beanScope = bean.attributeValue("scope");

            // 获取Class
            Class<?> clazz = Class.forName(className);
            // 优先级 id>name
            String beanName = StrUtil.isNotBlank(id) ? id : name;
            if (StrUtil.isBlank(beanName)) {
                beanName = StrUtil.lowerFirst(clazz.getSimpleName());
            }
            // 定义bean
            BeanDefinition beanDefinition = new BeanDefinition(clazz);
            // 设值初始化及销毁方法
            beanDefinition.setInitMethodName(initMethod);
            beanDefinition.setDestroyMethodName(destroyMethod);
            if (StrUtil.isNotBlank(beanScope)) {
                beanDefinition.setScope(beanScope);
            }
            List<Element> propertyList = bean.elements("property");
            // 读取属性并填充
            for (Element property : propertyList) {
                // 解析标签
                String attrName = property.attributeValue("name");
                String attrValue = property.attributeValue("value");
                String attrRef = property.attributeValue("ref");
                Object value = StrUtil.isNotBlank(attrRef) ? new BeanReference(attrRef) : attrValue;
                PropertyValue propertyValue = new PropertyValue(attrName, value);
                beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
            }
            if (getRegistry().containsBeanDefinition(beanName)) {
                throw new BeansException("Duplicate beanName ['" + beanName + "'] is not allowed");
            }
            // 注册BeanDefinition
            getRegistry().registerBeanDefinition(beanName, beanDefinition);
        }
    }

    private void scanPackage(String scanPath) {
        /**
         * 扫描包下的所有BeanDefinition
         */
        String[] basePackages = StrUtil.splitToArray(scanPath, StrUtil.COMMA);
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry());
        scanner.doScan(basePackages);
    }

}
